import Constant from './ui-constant';
import UISpace from './ui-space';

export default function UIInline(Base=class {}){
    return class UIInline extends Base{
        
        calculateShrinkFit( inlineChildren ) {
            const INNER_HEIGHT = this.getHeight() - (this.padding * 2 || 0);
            let iterations = 1;
            const heightTolerance = 0.075;
            const firstText = inlineChildren.find(inlineComponent => inlineComponent.isText);
            let minFontMultiplier = 0;
            let maxFontMultiplier = 1;
            let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1;
            let textHeight;
            do {
                textHeight = this.calculateHeight(inlineChildren, fontMultiplier);
                if ( textHeight > INNER_HEIGHT ) {
                    maxFontMultiplier = fontMultiplier;
                    fontMultiplier -= (maxFontMultiplier - minFontMultiplier) / 2;
                } else {
                    if ( fontMultiplier >= maxFontMultiplier ) { 
                        inlineChildren.forEach(inlineComponent => {
                            if ( inlineComponent.isInlineBlock ) return;
                            inlineComponent._fitFontSize = inlineComponent.getFontSize();
                        });
                        break;
                    }
                    if ( Math.abs(INNER_HEIGHT - textHeight) < heightTolerance )  break;
                    minFontMultiplier = fontMultiplier;
                    fontMultiplier += (maxFontMultiplier - minFontMultiplier) / 2;
                }
            } while ( ++ iterations <= 10 );
        }

        calculateAutoFit( inlineChildren ) {
            const INNER_HEIGHT = this.getHeight() - (this.padding * 2 || 0);
            let iterations = 1;
            const heightTolerance = 0.075;
            const firstText = inlineChildren.find(inlineComponent => inlineComponent.isText);
            let minFontMultiplier = 0;
            let maxFontMultiplier = 2;
            let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1;
            let textHeight;
            do {
                textHeight = this.calculateHeight(inlineChildren, fontMultiplier);
                if ( textHeight > INNER_HEIGHT ) {
                    maxFontMultiplier = fontMultiplier;
                    fontMultiplier -= (maxFontMultiplier - minFontMultiplier) / 2;
                } else {
                    if ( Math.abs(INNER_HEIGHT - textHeight) < heightTolerance )  break;
                    if ( Math.abs(fontMultiplier - maxFontMultiplier) < 5e-10 ) maxFontMultiplier *= 2;
                    minFontMultiplier = fontMultiplier;
                    fontMultiplier += (maxFontMultiplier - minFontMultiplier) / 2;
                }
            } while ( ++ iterations <= 10 );
        }
        computeLineWidth( line ){
            const firstInline = line[0];
            const lastInline = line[line.length-1];
            return Math.abs( firstInline.offsetX - (lastInline.offsetX+lastInline.width) );
        }
        calculateHeight( inlineChildren, fontMultiplier ) {
            inlineChildren.forEach( inlineComponent => {
                if ( inlineComponent.isInlineBlock ) return;
                inlineComponent._fitFontSize = inlineComponent.getFontSize() * fontMultiplier;
                inlineComponent.calculateInlines(inlineComponent._fitFontSize);
            });
            const lines = this.computeLines();
            const INTERLINE = this.getInterLine();
            const textHeight = lines.reduce( (offsetY, line)=> {
                return offsetY - line.lineHeight - INTERLINE;
            }, 0 ) + INTERLINE;
            return Math.abs( textHeight );
        }
        calculateGrowFit( inlineChildren ) {
            const INNER_HEIGHT = this.getHeight() - (this.padding * 2 || 0);
            let iterations = 1;
            const heightTolerance = 0.075;
            const firstText = inlineChildren.find( inlineComponent => inlineComponent.isText );
            let minFontMultiplier = 1;
            let maxFontMultiplier = 2;
            let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1;
            let textHeight;
            do {
                textHeight = this.calculateHeight(inlineChildren, fontMultiplier);
                if ( textHeight > INNER_HEIGHT ) {
                    if ( fontMultiplier <= minFontMultiplier ) { 
                        inlineChildren.forEach(inlineComponent => {
                            if ( inlineComponent.isInlineBlock ) return;
                            inlineComponent._fitFontSize = inlineComponent.getFontSize();
                        });
                        break;
                    }
                    maxFontMultiplier = fontMultiplier;
                    fontMultiplier -= (maxFontMultiplier - minFontMultiplier) / 2;
                } else {
                    if ( Math.abs(INNER_HEIGHT - textHeight) < heightTolerance )  break;
                    if ( Math.abs(fontMultiplier - maxFontMultiplier) < 5e-10 ) maxFontMultiplier *= 2;
                    minFontMultiplier = fontMultiplier;
                    fontMultiplier += (maxFontMultiplier - minFontMultiplier) / 2;
                }
            } while ( ++ iterations <= 10 );
        }
        calculateBestFit( bestFit ) {
            const inlineChildren = this.children.filter( (child)=> {
                return child.isInline ? true : false;
            });
            if ( inlineChildren.length == 0 )  return;
            switch(bestFit) {
                case Constant.VALUE_GROW:
                    this.calculateGrowFit(inlineChildren);
                    break;
                case Constant.VALUE_SHRINK:
                    this.calculateShrinkFit(inlineChildren);
                    break;
                case Constant.VALUE_AUTO:
                    this.calculateAutoFit(inlineChildren);
                    break;
            }
        }
        shouldFriendlyBreak( prevChar, lastInlineOffset, nextBreak, INNER_WIDTH ) {
            if ( !prevChar || !prevChar.glyph ) return false;
            if ( lastInlineOffset + nextBreak < INNER_WIDTH ) return false;
            const BREAK_ON = this.getBreakOn();
            return BREAK_ON.indexOf( prevChar.glyph ) > -1;

        }
        distanceToNextBreak( inlines, currentIdx, letterSpacing , accu ) {
            accu = accu || 0 ;
            if ( !inlines[ currentIdx ] ) return accu;
            const inline = inlines[ currentIdx ];
            const kerning = inline.kerning ? inline.kerning : 0;
            const xoffset = inline.xoffset ? inline.xoffset : 0;
            const xadvance = inline.xadvance ? inline.xadvance : inline.width ;
            if ( inline.lineBreak )  return accu + xadvance;
            return this.distanceToNextBreak(
                inlines,
                currentIdx + 1,
                letterSpacing,
                accu + xadvance + letterSpacing + xoffset + kerning
            );
        }
        computeLines() {
            const INNER_WIDTH = this.getWidth() - (this.padding * 2 || 0);
            const lines = [[]];
            this.children.filter( (child)=> {
                return child.isInline ? true : false;
            })
            .reduce( (lastInlineOffset, inlineComponent)=> {
                if ( !inlineComponent.inlines ) return;
                const fontSize = inlineComponent._fitFontSize || inlineComponent.getFontSize();
                const letterSpacing = inlineComponent.isText ? inlineComponent.getLetterSpacing() * fontSize : 0;
                const currentInlineInfo = inlineComponent.inlines.reduce( (lastInlineOffset, inline, i, inlines)=> {
                    const kerning = inline.kerning ? inline.kerning : 0;
                    const xoffset = inline.xoffset ? inline.xoffset : 0;
                    const xadvance = inline.xadvance ? inline.xadvance : inline.width;
                    const nextBreak = this.distanceToNextBreak( inlines, i , letterSpacing );
                    if (
                        lastInlineOffset + xadvance + xoffset + kerning > INNER_WIDTH ||
                        inline.lineBreak === Constant.VALUE_MANDATORY ||
                        this.shouldFriendlyBreak( inlines[ i - 1 ], lastInlineOffset, nextBreak, INNER_WIDTH )
                    ) {
                        lines.push([ inline ]);
                        inline.offsetX = xoffset;
                        if( inline.width === 0 ){
                            return 0;
                        }
                        return xadvance + letterSpacing;
                    }
                    lines[ lines.length - 1 ].push( inline );
                    inline.offsetX = lastInlineOffset + xoffset + kerning;
                    return lastInlineOffset + xadvance + kerning + letterSpacing;
                }, lastInlineOffset );
                return currentInlineInfo;
            }, 0 );
            lines.forEach( (line)=> {
                line.lineHeight = line.reduce( (height, inline) => {
                    const charHeight = inline.lineHeight !== undefined ? inline.lineHeight : inline.height;
                    return Math.max( height, charHeight );
                }, 0 );
                line.lineBase = line.reduce( (lineBase, inline) => {
                    const newLineBase = inline.lineBase !== undefined ? inline.lineBase : inline.height;
                    return Math.max( lineBase, newLineBase );
                }, 0 );
                line.width = 0;
                const lineHasInlines = line[0];
                if ( lineHasInlines ) {
                    const WHITE_SPACE = this.getWhiteSpace();
                    const whiteSpaceOffset = UISpace.collapseInlines(line, WHITE_SPACE);
                    line.forEach((inline) => {
                        inline.offsetX -= whiteSpaceOffset;
                    });
                    line.width = this.computeLineWidth(line);
                }
            });
            return lines;
        }
        computeInlinesPosition() {
            const INNER_WIDTH = this.getWidth() - (this.padding * 2 || 0);
            const INNER_HEIGHT = this.getHeight() - (this.padding * 2 || 0);
            const JUSTIFICATION = this.getJustifyContent();
            const ALIGNMENT = this.getAlignContent();
            const INTERLINE = this.getInterLine();
            const lines = this.computeLines();
            let textHeight = lines.reduce( (offsetY, line, i, arr)=> {
                const charAlignement = line.lineHeight - line.lineBase;
                line.forEach( (inline)=> {
                    inline.offsetY = offsetY - line.lineHeight + charAlignement + arr[0].lineHeight;
                });
                return offsetY - line.lineHeight - INTERLINE;
            }, 0 ) + INTERLINE;
            textHeight = Math.abs( textHeight );
            const justificationOffset = (()=> {
                switch ( JUSTIFICATION ) {
                    case Constant.VALUE_START: 
                        return (INNER_HEIGHT / 2) - lines[0].lineHeight;
                    case Constant.VALUE_END: 
                        return textHeight - lines[0].lineHeight - ( INNER_HEIGHT / 2 ) + (lines[ lines.length -1 ].lineHeight - lines[ lines.length -1 ].lineHeight) ;
                    case Constant.VALUE_CENTER: 
                        return (textHeight / 2) - lines[0].lineHeight;
                    default: 
                        console.warn(`justifyContent: '${ JUSTIFICATION }' is not valid`);
                }
            })();
            lines.forEach( (line)=> {
                line.forEach( (inline)=> {
                    inline.offsetY += justificationOffset;
                });
            });
            lines.forEach( (line)=> {
                const alignmentOffset = (()=> {
                    switch ( ALIGNMENT ) {
                        case Constant.VALUE_LEFT: 
                            return -INNER_WIDTH / 2;
                        case Constant.VALUE_RIGHT: 
                            return -line.width + (INNER_WIDTH / 2);
                        case Constant.VALUE_CENTER:
                            return -line.width / 2;
                        default: 
                            console.warn(`alignContent: '${ ALIGNMENT }' is not valid`);
                    }
                })();
                line.forEach( (char)=> {
                    char.offsetX += alignmentOffset;
                });
            });
        }

    }
}